package com.hqd.ch03.v43.web.servlet.view;

import com.hqd.ch03.utils.CollectionUtils;
import com.hqd.ch03.utils.ObjectUtils;
import com.hqd.ch03.v43.utils.BeanUtils;
import com.hqd.ch03.v43.utils.StringUtils;
import com.hqd.ch03.v43.web.http.HttpStatus;
import com.hqd.ch03.v43.web.servlet.HandlerMapping;
import com.hqd.ch03.v43.web.servlet.SmartView;
import com.hqd.ch03.v43.web.servlet.View;
import com.hqd.ch03.v43.web.servlet.support.RequestContextUtils;
import com.hqd.ch03.v43.web.utils.UriComponentsBuilder;
import com.hqd.ch03.v43.web.utils.WebUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Array;
import java.net.URLEncoder;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 重定向视图
 */
public class RedirectView extends AbstractUrlBasedView implements SmartView {

    /**
     * 是否是绝对路径
     */
    private boolean contextRelative = false;
    /**
     * 是否兼容 HTTP/1.0
     */
    private boolean http10Compatible = true;
    /**
     * 暴露模型属性
     */
    private boolean exposeModelAttributes = true;
    /**
     * URL 编码
     */
    private String encodingScheme;
    /**
     * 响应码
     */
    private HttpStatus statusCode;

    /**
     * 是否将当前请求的查询参数传递到目标 URL
     */
    private boolean propagateQueryParams = false;

    /**
     * 主机名
     */
    private String[] hosts;

    public RedirectView() {
        setExposePathVariables(false);
    }

    public RedirectView(String url) {
        super(url);
        setExposePathVariables(false);
    }

    public RedirectView(String url, boolean contextRelative) {
        super(url);
        this.contextRelative = contextRelative;
        setExposePathVariables(false);
    }

    public RedirectView(String url, boolean contextRelative, boolean http10Compatible) {
        super(url);
        this.contextRelative = contextRelative;
        this.http10Compatible = http10Compatible;
        setExposePathVariables(false);
    }

    public RedirectView(String url, boolean contextRelative, boolean http10Compatible, boolean exposeModelAttributes) {
        super(url);
        this.contextRelative = contextRelative;
        this.http10Compatible = http10Compatible;
        this.exposeModelAttributes = exposeModelAttributes;
        setExposePathVariables(false);
    }

    @Override
    protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request,
                                           HttpServletResponse response) throws IOException {

        String targetUrl = createTargetUrl(model, request);
        targetUrl = updateTargetUrl(targetUrl, model, request, response);
        //保存到闪存中
        RequestContextUtils.saveOutputFlashMap(targetUrl, request, response);
        // 重定向
        sendRedirect(request, response, targetUrl, this.http10Compatible);
    }

    /**
     * 构建重定向url
     *
     * @param model
     * @param request
     * @return
     * @throws UnsupportedEncodingException
     */
    protected final String createTargetUrl(Map<String, Object> model, HttpServletRequest request)
            throws UnsupportedEncodingException {

        StringBuilder targetUrl = new StringBuilder();
        if (this.contextRelative && getUrl().startsWith("/")) {
            targetUrl.append(getContextPath(request));
        }
        targetUrl.append(getUrl());

        String enc = this.encodingScheme;
        /**
         * 编码为空，使用请求编码
         */
        if (enc == null) {
            enc = request.getCharacterEncoding();
        }
        /**
         * 请求编码还是为空，使用默认编码
         */
        if (enc == null) {
            enc = WebUtils.DEFAULT_CHARACTER_ENCODING;
        }
        /**
         * 是否追加参数
         */
        if (isPropagateQueryProperties()) {
            appendCurrentQueryParams(targetUrl, request);
        }
        /**
         * 是否暴露模型数据
         */
        if (this.exposeModelAttributes) {
            appendQueryProperties(targetUrl, model, enc);
        }
        return targetUrl.toString();
    }

    private String getContextPath(HttpServletRequest request) {
        String contextPath = request.getContextPath();
        while (contextPath.startsWith("//")) {
            contextPath = contextPath.substring(1);
        }
        return contextPath;
    }


    @SuppressWarnings("unchecked")
    private Map<String, String> getCurrentRequestUriVariables(HttpServletRequest request) {
        String name = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;
        Map<String, String> uriVars = (Map<String, String>) request.getAttribute(name);
        return (uriVars != null) ? uriVars : Collections.emptyMap();
    }

    /**
     * 将当前请求的查询字符串附加到目标重定向URL
     *
     * @param targetUrl
     * @param request
     */
    protected void appendCurrentQueryParams(StringBuilder targetUrl, HttpServletRequest request) {
        /**
         * 获取请求字符串
         */
        String query = request.getQueryString();
        if (StringUtils.hasText(query)) {
            String fragment = null;
            /**
             * 是否存在锚点，如果存在，暂存起来
             */
            int anchorIndex = targetUrl.indexOf("#");
            if (anchorIndex > -1) {
                fragment = targetUrl.substring(anchorIndex);
                targetUrl.delete(anchorIndex, targetUrl.length());
            }
            /**
             * 不存在?则添加？及参数
             */
            if (targetUrl.toString().indexOf('?') < 0) {
                targetUrl.append('?').append(query);
            } else {//已存在参数，则添加&
                targetUrl.append('&').append(query);
            }
            /**
             * 将锚点拼接在最后
             */
            if (fragment != null) {
                targetUrl.append(fragment);
            }
        }
    }

    /**
     * 将模型数据添加到重定向url参数中
     *
     * @param targetUrl
     * @param model
     * @param encodingScheme
     * @throws UnsupportedEncodingException
     */
    protected void appendQueryProperties(StringBuilder targetUrl, Map<String, Object> model, String encodingScheme)
            throws UnsupportedEncodingException {

        /**
         * 是否存在锚点
         */
        String fragment = null;
        int anchorIndex = targetUrl.indexOf("#");
        if (anchorIndex > -1) {
            fragment = targetUrl.substring(anchorIndex);
            targetUrl.delete(anchorIndex, targetUrl.length());
        }

        /**
         * 是否存在？
         */
        boolean first = (targetUrl.toString().indexOf('?') < 0);
        for (Map.Entry<String, Object> entry : queryProperties(model).entrySet()) {
            Object rawValue = entry.getValue();
            Collection<?> values;
            if (rawValue != null && rawValue.getClass().isArray()) {
                values = CollectionUtils.arrayToList(rawValue);
            } else if (rawValue instanceof Collection) {
                values = ((Collection<?>) rawValue);
            } else {
                values = Collections.singleton(rawValue);
            }
            for (Object value : values) {
                if (first) {
                    targetUrl.append('?');
                    first = false;
                } else {
                    targetUrl.append('&');
                }
                String encodedKey = urlEncode(entry.getKey(), encodingScheme);
                String encodedValue = (value != null ? urlEncode(value.toString(), encodingScheme) : "");
                targetUrl.append(encodedKey).append('=').append(encodedValue);
            }
        }

        /**
         * 添加锚点
         */
        if (fragment != null) {
            targetUrl.append(fragment);
        }
    }

    /**
     * 遍历模型数据，查询符合条件的值
     *
     * @param model
     * @return
     */
    protected Map<String, Object> queryProperties(Map<String, Object> model) {
        Map<String, Object> result = new LinkedHashMap<>();
        model.forEach((name, value) -> {
            if (isEligibleProperty(name, value)) {
                result.put(name, value);
            }
        });
        return result;
    }

    /**
     * 判断model中的属性，是否符合条件
     *
     * @param key
     * @param value
     * @return
     */
    protected boolean isEligibleProperty(String key, Object value) {
        /**
         * 空的直接返回
         */
        if (value == null) {
            return false;
        }
        /**
         * 简单类型直接返回true
         */
        if (isEligibleValue(value)) {
            return true;
        }
        /**
         * 是否是数组
         */
        if (value.getClass().isArray()) {
            int length = Array.getLength(value);
            if (length == 0) {
                return false;
            }
            for (int i = 0; i < length; i++) {
                Object element = Array.get(value, i);
                if (!isEligibleValue(element)) {
                    return false;
                }
            }
            return true;
        }
        /**
         * 是否是集合
         */
        if (value instanceof Collection) {
            Collection<?> coll = (Collection<?>) value;
            if (coll.isEmpty()) {
                return false;
            }
            for (Object element : coll) {
                if (!isEligibleValue(element)) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }

    /**
     * 是否是简单类型
     *
     * @param value
     * @return
     */
    protected boolean isEligibleValue(Object value) {
        return (value != null && BeanUtils.isSimpleValueType(value.getClass()));
    }

    /**
     * 重新编码
     *
     * @param input
     * @param encodingScheme
     * @return
     * @throws UnsupportedEncodingException
     */
    protected String urlEncode(String input, String encodingScheme) throws UnsupportedEncodingException {
        return URLEncoder.encode(input, encodingScheme);
    }

    protected String updateTargetUrl(String targetUrl, Map<String, Object> model,
                                     HttpServletRequest request, HttpServletResponse response) {
        return targetUrl;
    }

    /**
     * 发送重定向请求
     *
     * @param request
     * @param response
     * @param targetUrl
     * @param http10Compatible
     * @throws IOException
     */
    protected void sendRedirect(HttpServletRequest request, HttpServletResponse response,
                                String targetUrl, boolean http10Compatible) throws IOException {

        String encodedURL = (isRemoteHost(targetUrl) ? targetUrl : response.encodeRedirectURL(targetUrl));
        if (http10Compatible) {//HTTP1.0
            HttpStatus attributeStatusCode = (HttpStatus) request.getAttribute(View.RESPONSE_STATUS_ATTRIBUTE);
            if (this.statusCode != null) {
                response.setStatus(this.statusCode.value());
                response.setHeader("Location", encodedURL);
            } else if (attributeStatusCode != null) {
                response.setStatus(attributeStatusCode.value());
                response.setHeader("Location", encodedURL);
            } else {
                response.sendRedirect(encodedURL);
            }
        } else {//HTTP1.1
            HttpStatus statusCode = getHttp11StatusCode(request, response, targetUrl);
            response.setStatus(statusCode.value());
            response.setHeader("Location", encodedURL);
        }
    }

    /**
     * 远程主机是否匹配
     *
     * @param targetUrl
     * @return
     */
    protected boolean isRemoteHost(String targetUrl) {
        if (ObjectUtils.isEmpty(getHosts())) {
            return false;
        }
        String targetHost = UriComponentsBuilder.fromUriString(targetUrl).build().getHost();
        if (!StringUtils.hasLength(targetHost)) {
            return false;
        }
        for (String host : getHosts()) {
            if (targetHost.equals(host)) {
                return false;
            }
        }
        return true;
    }

    /**
     * 获取响应码
     *
     * @param request
     * @param response
     * @param targetUrl
     * @return
     */
    protected HttpStatus getHttp11StatusCode(
            HttpServletRequest request, HttpServletResponse response, String targetUrl) {

        if (this.statusCode != null) {
            return this.statusCode;
        }
        HttpStatus attributeStatusCode = (HttpStatus) request.getAttribute(View.RESPONSE_STATUS_ATTRIBUTE);
        if (attributeStatusCode != null) {
            return attributeStatusCode;
        }
        return HttpStatus.SEE_OTHER;
    }

    public void setContextRelative(boolean contextRelative) {
        this.contextRelative = contextRelative;
    }

    public void setHttp10Compatible(boolean http10Compatible) {
        this.http10Compatible = http10Compatible;
    }

    public void setExposeModelAttributes(final boolean exposeModelAttributes) {
        this.exposeModelAttributes = exposeModelAttributes;
    }

    public void setEncodingScheme(String encodingScheme) {
        this.encodingScheme = encodingScheme;
    }

    public void setStatusCode(HttpStatus statusCode) {
        this.statusCode = statusCode;
    }


    public void setPropagateQueryParams(boolean propagateQueryParams) {
        this.propagateQueryParams = propagateQueryParams;
    }

    public boolean isPropagateQueryProperties() {
        return this.propagateQueryParams;
    }

    public String[] getHosts() {
        return this.hosts;
    }

    public void setHosts(String... hosts) {
        this.hosts = hosts;
    }

    @Override
    public boolean isRedirectView() {
        return true;
    }

}
